using System;
using System.Linq;
using System.Linq.Expressions;
using Magiq.Tests.Support;
using Magiq.Tests.TestModel;
using NUnit.Framework;

namespace Magiq.Tests {
    [TestFixture]
    public abstract class BaseMagiqProviderInsertsTest : BaseMagiqProviderTest
    {
        private IQueryable<TestClass1> dest;

        protected override void Initialize()
        {
            dest = Configurator.GetQueryable<TestClass1>();
        }
        [Test]
        public void ShouldInsertWhereThatUsesSelfReference()
        {
            TestInsert(x => x.SelfReference != null && x.SelfReference.IntProperty > 3,
                       x =>
                       new TestClass1
                         {
                             AnotherStringProperty = x.StringProperty,
                             IntProperty = x.AnotherIntProperty,
                             AnotherIntProperty = 4,
                             StringProperty = "lala"
                         }, dest);
        }

        private void TestInsert<T>(Expression<Func<TestClass1, bool>> where, Expression<Func<TestClass1, T>> insert,
                                   IQueryable<T> destination)
          where T : class
        {
            var count = TestClass1s.Where(where).Count();
            var prevDestinationCount = destination.Count();
            TestClass1s.Where(where).Insert(insert).Into(destination).Should(Be.EqualTo(count));
            destination.Count().Should(Be.EqualTo(prevDestinationCount + count));
        }

        [Test]
        public void ShouldInsertAll()
        {
            var prevCount = dest.Count();
            var count = TestClass1s.Count();
            var insertCount = TestClass1s.Insert(x => new TestClass1
                                                        {
                                                            AnotherStringProperty = x.StringProperty,
                                                            IntProperty = x.AnotherIntProperty,
                                                            AnotherIntProperty = 4,
                                                            StringProperty = "lala"
                                                        }).Into(dest);
            insertCount.Should(Be.EqualTo(count));
            dest.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertAllIntoItself()
        {
            var count = TestClass1s.Count();
            TestClass1s.Insert(x => new TestClass1
                                      {
                                          AnotherStringProperty = x.StringProperty,
                                          IntProperty = x.AnotherIntProperty,
                                          AnotherIntProperty = 4,
                                          StringProperty = "lala"
                                      }).Into(TestClass1s).Should(Be.EqualTo(count));
            TestClass1s.Count().Should(Be.EqualTo(count * 2));
        }

        [Test]
        public void ShouldInsertComplexWhere()
        {
            TestInsert(x => (x.StringProperty == "sarasa" && x.IntProperty >= 3) ||
                            x.AnotherIntProperty < 12 &&
                            x.AnotherStringProperty != null, x => new TestClass1
                                                                    {
                                                                        AnotherStringProperty = x.StringProperty,
                                                                        IntProperty = x.AnotherIntProperty,
                                                                        AnotherIntProperty = 4,
                                                                        StringProperty = "lala"
                                                                    }, dest);
        }

        [Test]
        public void ShouldInsertNotInheritedSubclasses()
        {
            var dest2 = Configurator.GetQueryable<TestClass2>();
            var prevCount = dest2.Count();
            var count = TestClass2s.Count();
            var insertCount = TestClass2s.Insert(x => new TestClass2
                                                        {
                                                            IntProperty = x.IntProperty + 2,
                                                            StringProperty = "lala",
                                                        }).Into(dest2);
            insertCount.Should(Be.EqualTo(count));
            dest2.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertReferences()
        {
            var prevCount = dest.Count();
            var count = TestClass1s.Count();
            var insertCount = TestClass1s.Insert(x => new TestClass1
                                                        {
                                                            AnotherStringProperty = x.StringProperty,
                                                            IntProperty = x.AnotherIntProperty,
                                                            AnotherIntProperty = 4,
                                                            StringProperty = "lala",
                                                            Reference = x.Reference
                                                        }).Into(dest);
            insertCount.Should(Be.EqualTo(count));
            dest.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertSubclasses()
        {
            var prevCount = dest.Count();
            var count = TestClass1s.Count();
            var insertCount = TestClass1s.Insert(x => new TestClass1Inheritor
                                                        {
                                                            AnotherStringProperty = x.StringProperty,
                                                            IntProperty = x.AnotherIntProperty,
                                                            AnotherIntProperty = 4,
                                                            StringProperty = "lala",
                                                            InheritorProperty = "testing"
                                                        }).Into(dest);
            insertCount.Should(Be.EqualTo(count));
            dest.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertWhereIntoItself()
        {
            TestInsert(x => (x.StringProperty == "sarasa" && x.IntProperty >= 3) ||
                            x.AnotherIntProperty < 12 &&
                            x.AnotherStringProperty != null, x => new TestClass1
                                                                    {
                                                                        AnotherStringProperty = x.StringProperty,
                                                                        IntProperty = x.AnotherIntProperty,
                                                                        AnotherIntProperty = 4,
                                                                        StringProperty = "lala"
                                                                    }, TestClass1s);
        }

        [Test]
        public void ShouldInsertWhereThatUsesAnotherIntProperty()
        {
            TestInsert(x => x.IntProperty == x.AnotherIntProperty, x => new TestClass1
                                                                          {
                                                                              AnotherStringProperty =
                                                                                x.StringProperty,
                                                                              IntProperty = x.AnotherIntProperty,
                                                                              AnotherIntProperty = 4,
                                                                              StringProperty = "lala"
                                                                          }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesAnotherStringProperty()
        {
            TestInsert(x => x.StringProperty == x.AnotherStringProperty, x => new TestClass1
                                                                                {
                                                                                    AnotherStringProperty =
                                                                                      x.StringProperty,
                                                                                    IntProperty = x.AnotherIntProperty,
                                                                                    AnotherIntProperty = 4,
                                                                                    StringProperty = "lala"
                                                                                }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesComposedIntProperty()
        {
            TestInsert(x => x.IntProperty == x.AnotherIntProperty + 3, x => new TestClass1
                                                                              {
                                                                                  AnotherStringProperty =
                                                                                    x.StringProperty,
                                                                                  IntProperty = x.AnotherIntProperty,
                                                                                  AnotherIntProperty = 4,
                                                                                  StringProperty = "lala"
                                                                              }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesComposedStringProperty()
        {
            TestInsert(x => x.StringProperty == x.AnotherStringProperty + "la", x => new TestClass1
                                                                                       {
                                                                                           AnotherStringProperty =
                                                                                             x.StringProperty,
                                                                                           IntProperty = x.AnotherIntProperty,
                                                                                           AnotherIntProperty = 4,
                                                                                           StringProperty = "lala"
                                                                                       }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesIntProperty()
        {
            TestInsert(x => x.IntProperty == 3, x => new TestClass1
                                                       {
                                                           AnotherStringProperty =
                                                             x.StringProperty,
                                                           IntProperty = x.AnotherIntProperty,
                                                           AnotherIntProperty = 4,
                                                           StringProperty = "lala"
                                                       }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntProperty()
        {
            TestInsert(x => x.NullableIntProperty == 3,
                       x =>
                       new TestClass1
                         {
                             AnotherStringProperty = x.StringProperty,
                             IntProperty = x.AnotherIntProperty,
                             AnotherIntProperty = 4,
                             StringProperty = "lala"
                         }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyNull()
        {
            TestInsert(x => x.NullableIntProperty == null,
                       x =>
                       new TestClass1
                         {
                             AnotherStringProperty = x.StringProperty,
                             IntProperty = x.AnotherIntProperty,
                             AnotherIntProperty = 4,
                             StringProperty = "lala"
                         }, dest);
        }


        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyToNull()
        {
            TestInsert(x => x.NullableIntProperty == null,
                       x =>
                       new TestClass1
                         {
                             AnotherStringProperty = x.StringProperty,
                             IntProperty = x.AnotherIntProperty,
                             AnotherIntProperty = 4,
                             StringProperty = "lala"
                         }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyWithIntProperty()
        {
            TestInsert(x => x.NullableIntProperty == x.IntProperty,
                       x =>
                       new TestClass1
                         {
                             AnotherStringProperty = x.StringProperty,
                             IntProperty = x.AnotherIntProperty,
                             AnotherIntProperty = 4,
                             StringProperty = "lala"
                         }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesReference()
        {
            var r = TestClass2s.First();
            TestInsert(x => x.Reference == r, x => new TestClass1
                                                     {
                                                         AnotherStringProperty =
                                                           x.StringProperty,
                                                         IntProperty = x.AnotherIntProperty,
                                                         AnotherIntProperty = 4,
                                                         StringProperty = "lala"
                                                     }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesReferenceNull()
        {
            TestInsert(x => x.Reference == null,
                       x =>
                       new TestClass1
                         {
                             AnotherStringProperty = x.StringProperty,
                             IntProperty = x.AnotherIntProperty,
                             AnotherIntProperty = 4,
                             StringProperty = "lala"
                         }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesStringProperty()
        {
            TestInsert(x => x.StringProperty == "sarasa", x => new TestClass1
                                                                 {
                                                                     AnotherStringProperty = x.StringProperty,
                                                                     IntProperty = x.AnotherIntProperty,
                                                                     AnotherIntProperty = 4,
                                                                     StringProperty = "lala"
                                                                 }, dest);
        }

        [Test]
        public void ShouldInsertWhereThatUsesStringPropertyNull()
        {
            TestInsert(x => x.StringProperty == null, x => new TestClass1
                                                             {
                                                                 AnotherStringProperty =
                                                                   x.StringProperty,
                                                                 IntProperty = x.AnotherIntProperty,
                                                                 AnotherIntProperty = 4,
                                                                 StringProperty = "lala"
                                                             }, dest);
        }

        [Test]
        public void ShouldInsertGroupedByValue()
        {
            var items = TestClass1s.Where(x => x.AnotherIntProperty > 3).GroupBy(x => x.AnotherStringProperty).Select(y => y.Count());
            items.Insert(x => new TestClass3
            {
                Text = x.ToString()
            }).Into(TestClass3s);
        }
        [Test]
        public void ShouldInsertGroupedByAnnonymousType()
        {
            var items =
                TestClass1s.Where(x => x.AnotherIntProperty > 3).GroupBy(x => x.AnotherStringProperty)
                .Select(y => new { Prop = y.Count() });
            items.Insert(x => new TestClass3
            {
                Text = x.Prop.ToString()
            }).Into(TestClass3s);
        }
        
        [Test]
        public void ShouldInsertOrderBy()
        {
            var items = TestClass1s.Where(x => x.AnotherIntProperty > 3).OrderBy(x => x.AnotherStringProperty);
            items.Insert(x => new TestClass3
                                  {
                                      Text = x.AnotherStringProperty
                                  }).Into(TestClass3s);

            var sortedItems = TestClass3s.OrderBy(x => x.Text).ToList();
            var retrievedItems = TestClass3s.ToList();

            for (var i = 0; i < sortedItems.Count; i++)
                retrievedItems[i].Id.Should(Be.EqualTo(sortedItems[i].Id));
        }
    }
}